package microcredit.gui.selector;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import microcredit.gui.base.BaseAction;
import microcredit.gui.base.BaseDialog;
import microcredit.gui.table.BeanTable;
import microcredit.gui.table.BeanTable.SelectionMode;
import microcredit.gui.table.EnumBeanTableModel;
import microcredit.gui.table.TableFactory;
import microcredit.gui.util.ColorConstants;
import microcredit.gui.util.DialogUtilities;
import microcredit.gui.util.ExceptionDialog;
import microcredit.gui.util.FixedLengthDocument;
import microcredit.gui.util.UIUtil;
import microcredit.model.ValidationException;
import net.miginfocom.swing.MigLayout;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator;
import org.jdesktop.swingx.border.MatteBorderExt;


public class SelectorPanel<E> extends AbstractButtonPanel {

	private static final Log LOGGER = LogFactory.getLog(SelectorPanel.class);
	
	private JScrollPane selectorScrollPane;
	private BeanTable<E> selectorTable;
	
	private final ILoadable<E> loadable;
	private final IOpenable<E> openable;
	private final INewable<E> newable;
	private final IDeletable<E> deletable;
	private final ISelectable<E> selectable;
	private final IChooseable<E> chooseable;
	private final IFilterable<E> filterable;
	
	private BaseAction openAction;
	private BaseAction newAction;
	private BaseAction deleteAction;
	private BaseAction refreshAction;
	private BaseAction selectAction;
	private BaseAction chooseAction;
	
	private List<E> selectedElement;
	private List<E> originalEntities;
	private List<E> filteredEntities;
	
	public SelectorPanel(Class<E> entityClass, 
			ILoadable<E> loadable, 
			IOpenable<E> openable, 
			INewable<E> newable, 
			IDeletable<E> deletable,
			ISelectable<E> selectable,
			IChooseable<E> chooseable,
			IFilterable<E> filterable, Class<? extends EnumBeanTableModel<E, ?>> tableModelClass
			) {
		this.filterable = filterable;
		this.loadable = loadable;
		this.openable = openable;
		this.newable = newable;
		this.deletable = deletable;
		this.selectable = selectable;
		this.chooseable = chooseable;
		
		init();
		updateActions();
		
		TableFactory.fill(selectorTable, entityClass, tableModelClass);
		emulateActionWithoutThrow(refreshAction);
		
		runFilters();
		setBackground(ColorConstants.BACKGROUND_TABBED_PANE);
	}
	
	public BaseAction getDeleteAction() {
		return deleteAction;
	}
	
	public void refresh() {
		emulateActionWithoutThrow(refreshAction);
	}
	
	public List<E> getSelectedElements() {
		return selectedElement;
	}
	
	@SuppressWarnings("unchecked")
	public void setSelectedElement(E element) {
		setSelectedElements(Arrays.asList(element));
	}
	
	public void setSelectedElements(List<E> elements) {
		getSelectorTable().setSelectedElements(elements);
	}
	
	public E getSelectedElement() {
		List<E> elements = getSelectedElements();
		if (elements != null && elements.size() > 0) {
			return elements.get(0);
		}
		return null;
	}
	
	public void focusDefaultComponent() {
		getSelectorTable().requestFocus();
		getSelectorTable().getSelectionModel().setSelectionInterval(0, 0);
	}
	
	public void setSelectionMode(SelectionMode mode) {
		getSelectorTable().setCheckBoxSelectionMode(mode);
	}
	
//	private JPopupMenu createPopupMenu() {
//		JPopupMenu popupMenu = new JPopupMenu();
//		addToPopup(popupMenu, newAction);
//		addToPopup(popupMenu, openAction);
//		addToPopup(popupMenu, deleteAction);
//		addToPopup(popupMenu, chooseAction);
//		return popupMenu;
//	}
//	private void addToPopup(JPopupMenu menu, BaseAction action) {
//		if (action != null) {
//			menu.add(action);
//		}
//	}
	
	private void updateActions() {
		removeAllActions();
		
		refreshAction = new RefreshAction(this);
		
		if (deletable != null) {
			deleteAction = new DeleteAction(this);
		}
		if (newable != null) {
			newAction = new NewAction(this);
		}
		if (openable != null) {
			openAction = new OpenAction(this);
		}
		if (selectable  != null) {
			selectAction = new SelectAction(this);
		}
		
		if (chooseable !=null) {
			chooseAction = new ChooseAction(this);
		}
		
//		addAction(refreshAction);
		addAction(chooseAction);
		addAction(newAction);
		addAction(openAction);
		
		addExpander();
		
		addAction(selectAction);
		addAction(deleteAction);
		
		updateState();
	}
	
	private void init() {
		getMainPanel().setLayout(new MigLayout("", "[grow]", "0[]0[grow]0"));
		
		getMainPanel().add(createFilterPanel(), "cell 0 0,grow");
		getMainPanel().add(getSelectorScrollPane(), "cell 0 1,grow");
	}

	private JPanel createFilterPanel() {
		final JPanel filterPanel = new JPanel(new MigLayout("", "3[]3[]0[]3[]0[]3[]0[]3", "0[]0"));
		if (filterable != null) {
			
			List<IFilter<E>> filters = filterable.getFilters();
			if(filters == null || filters.size() <= 0) {
				return filterPanel;
			}
			
			filterPanel.setBackground(new Color(UIManager.getColor("controlShadow").getRGB()));
			MatteBorder b =new MatteBorderExt(1, 1, 0, 1, Color.GRAY);
			EmptyBorder b2 = new EmptyBorder(0, 2, 0, 2);
			filterPanel.setBorder(new CompoundBorder(b2, b));
			
			
			if (filters != null && filters.size() > 0) {
				int index = 0;
				JLabel titleLabel = new JLabel("Filter ");
				filterPanel.add(titleLabel, "cell "+index+" 0");
				index++;

				for (final IFilter<E> filter : filters) {
					JLabel jlabel = new JLabel(filter.getLabel()+":");
					jlabel.setFont(jlabel.getFont().deriveFont(10f));
					filterPanel.add(jlabel, "cell "+index+" 0");
					index++;
					
					JComponent filterComponent = createFilterComponent(filter);
					filterPanel.add(filterComponent, "cell "+index+" 0");
					
					index++;
					
					jlabel.setLabelFor(filterComponent);
					addFilter(filter);
				}
			}
			
			filterPanel.add(new JLabel(""), "pushx");
			JButton resetButton = new JButton(new ImageIcon(SelectorPanel.class.getResource("/microcredit/icons/x16/undo.png")));
			resetButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					Component[] components = filterPanel.getComponents();
					for (Component component : components) {
						if (component instanceof JTextField) {
							((JTextField) component).setText("");
						} else if (component instanceof JComboBox) {
							((JComboBox) component).setSelectedIndex(0);
						}
						
					}
				}
			});	
			resetButton.setToolTipText("Reset filters");
			resetButton.setBorderPainted(false);
			filterPanel.add(resetButton);
			
		}
		return filterPanel;
	}
	
	private JComponent createFilterComponent( 
			final IFilter<E> filter) {
		JComponent component = null;
		List<?> values = filter.getValues();
		if (values != null) {
			final JComboBox comboBox = new JComboBox();
			DefaultComboBoxModel model = new DefaultComboBoxModel();
			model.addElement(" - all - ");
			for (Object object : values) {
				model.addElement(object);
			}
			comboBox.setModel(model);
			AutoCompleteDecorator.decorate(comboBox);
			((JTextField)comboBox.getEditor().getEditorComponent()).setColumns(1);
			if (filter.getSelectedValue() != null) {
				Object selected = filter.getSelectedValue();
				for (Object object : values) {
					if (object.equals(selected)) {
						selected = object;
						break;
					}
				}
				model.setSelectedItem(selected);
				
				comboBox.setSelectedItem(selected);
				objectFilterUpdate(filter, selected, false);
			}
			
			comboBox.addItemListener(new ItemListener() {
				@Override
				public void itemStateChanged(ItemEvent e) {
					if (comboBox.getSelectedIndex() == 0) {
						objectFilterUpdate(filter, null, true);
					} else {
						objectFilterUpdate(filter, comboBox.getSelectedItem(), true);
					}
				}
			});
			comboBox.setFont(comboBox.getFont().deriveFont(10f));
			comboBox.setRenderer(new DefaultListCellRenderer() {
				@Override
				public Component getListCellRendererComponent(JList list,
						Object value, int index, boolean isSelected,
						boolean cellHasFocus) {
					JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected,
							cellHasFocus);
					if (value != null) {
						String toStringValue = value.toString();
						if (toStringValue.length() > 20) {
							label.setText(toStringValue.substring(0,19)+"...");
						}
					}
					return label;
				}
			});
			
			component = comboBox;
		} else {
			final JTextField textField = new JTextField();
			textField.setDocument(new FixedLengthDocument(255));
			textField.setColumns(10);
			textField.setFont(textField.getFont().deriveFont(9f));
			textField.getDocument().addDocumentListener(new DocumentListener() {
				@Override
				public void removeUpdate(DocumentEvent e) {
					textFilterUpdate(filter, textField.getText());
				}
				@Override
				public void insertUpdate(DocumentEvent e) {
					textFilterUpdate(filter, textField.getText());
				}
				@Override
				public void changedUpdate(DocumentEvent e) {
					textFilterUpdate(filter, textField.getText());
				}
			});
			component = textField;
		}
		return component;
	}
	
	private Map<IFilter<E>, Object> filters;
	
	private void loadEntities(List<E> entities) {
		originalEntities = entities;
		runFilters();
	}
	
	private void addFilter(IFilter<E> filter) {
		if (filters == null) {
			filters = new HashMap<IFilter<E>, Object>();
		}
		if (!filters.containsKey(filter)) {
			filters.put(filter, null);
		}
	}
	
	private void objectFilterUpdate(IFilter<E> filter, Object value, boolean update) {
		filters.put(filter, value);
		if (update) {
			runFilters();
		}
	}
	
	private void textFilterUpdate(IFilter<E> filter, String text) {
		if (text.isEmpty()) {
			text = null;
		}
		filters.put(filter, text);
		
		runFilters();
	}
	
	private void runFilters() {
		if (originalEntities != null && originalEntities.size() > 0) {
			filteredEntities = new ArrayList<E>(originalEntities.size());
			filteredEntities.addAll(originalEntities);
			if (filters != null) {
				for (IFilter<E> filter : filters.keySet()) {
					Object exp = filters.get(filter);
					if (exp != null) {
						filter.filter(exp, filteredEntities);
					}
				}
			}
			getSelectorTable().load(filteredEntities);
		} else {
			getSelectorTable().load(null);
		}
	}
	
	private JScrollPane getSelectorScrollPane() {
		if (selectorScrollPane == null) {
			selectorScrollPane = new JScrollPane();
			selectorScrollPane.setViewportView(getSelectorTable());
		}
		return selectorScrollPane;
	}
	
	private BeanTable<E> getSelectorTable() {
		if (selectorTable == null) {
			selectorTable = new BeanTable<E>();
			selectorTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
				@Override
				public void valueChanged(ListSelectionEvent e) {
					updateState();
				}
			});
			selectorTable.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
//					if (e.getButton() == 3) {
//						createPopupMenu().show(SelectorPanel.this, e.getX(), e.getY());
//					} else 
						if (e.getClickCount() == 2) {
						try {
							emulateAction(openAction);
						} catch (Throwable e1) {
							ExceptionDialog.handleException(SelectorPanel.this, e1);
						}
					}
				}
			});
			if (selectable != null) {
				selectorTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
				
//				selectorTable.addMouseListener(new MouseAdapter() {
//					@Override
//					public void mouseClicked(MouseEvent e) {
//						if (e.getClickCount() == 2) {
//							try {
//								emulateAction(selectAction);
//							} catch (Throwable e1) {
//								ExceptionDialog.handleException(SelectorPanel.this, e1);
//							}
//						}
//					}
//				});
			}
		}
		return selectorTable;
	}
	
	private class SelectAction extends BaseAction {
		public SelectAction(Component parent) {
			super(parent);
			setName("Select");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK));
		}
		@Override
		public void perform(ActionEvent e) throws Throwable {
			List<E> elem  = getSelectorTable().getSelectedElements();
			selectedElement = elem;
			selectable.select(elem);
		}
	}
	
	private class DeleteAction extends BaseAction {
		public DeleteAction(Component parent) {
			super(parent);
			setName("Delete");
			setIcon("x16/delete2.png");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
		}
		@Override
		public void perform(ActionEvent e) throws Throwable {
			List<E> selected = getSelectorTable().getSelectedElements();
			
			String message = ("Are you sure to delete the following elements?\n\n");
			
			int option = JOptionPane.showConfirmDialog(SwingUtilities.getWindowAncestor(SelectorPanel.this), 
					UIUtil.createListMessage(message, selected));
			if (option != JOptionPane.YES_OPTION) {
				return;
			}
			
			List<E> failed = new ArrayList<E>();
			List<String> messages = new ArrayList<String>();
			for (E e2 : selected) {
				try {
					deletable.delete(e2);
					messages.add("Success: "+e2+" - Deleted successfully");
				} catch (Throwable t) {
					if (!(t instanceof ValidationException)) {
						LOGGER.error(t.getMessage(), t);
					}
					failed.add(e2);
					messages.add("Failed: "+e2+" - "+(t.getMessage() != null ? t.getMessage() : " for no reason"));
				}
			}
			if (failed.size() == selected.size()) {
				JOptionPane.showMessageDialog(SwingUtilities.getWindowAncestor(SelectorPanel.this), 
						UIUtil.createListMessage("Error: The delete operation failed for the following reasons: ", messages), "Error", JOptionPane.ERROR_MESSAGE);
			} else if (failed.size() > 0) {
				JOptionPane.showMessageDialog(SwingUtilities.getWindowAncestor(SelectorPanel.this), 
						UIUtil.createListMessage("The delete operation failed partially please see below for further details:", messages)
						, "Partial error", JOptionPane.ERROR_MESSAGE);
			} else {
				JOptionPane.showMessageDialog(SwingUtilities.getWindowAncestor(SelectorPanel.this),
						UIUtil.createListMessage("The following elements were deleted:", messages)
						, "Success", JOptionPane.INFORMATION_MESSAGE);
			}
			emulateAction(refreshAction);
		}
		@Override
		public void updateState() throws Throwable {
			this.setEnabled(getSelectorTable().getSelectedElement() != null);
		}
	}
	
	
	
	private class RefreshAction extends BaseAction {
		
		public RefreshAction(Component parent) {
			super(parent);
			setName("Refresh");
			setIcon("x16/refresh.png");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
		}
		
		@Override
		public void perform(ActionEvent e) throws Throwable {
			loadEntities(loadable.load());
		}
	}
	
	private class NewAction extends BaseAction {

		public NewAction(Component parent) {
			super(parent);
			setName("New");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_DOWN_MASK));
			setIcon("x16/row_add.png");
		}

		@Override
		public void perform(ActionEvent e) throws Throwable {
			if (newable != null) {
				E ret = newable.newEntity(SelectorPanel.this);
				if (ret != null) {
					emulateAction(refreshAction);
				}
			}
		}
		
	}
	
	private class ChooseAction extends BaseAction {

		public ChooseAction(Component parent) {
			super(parent);
			setName("Choose");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_DOWN_MASK));
		}

		@Override
		public void perform(ActionEvent e) throws Throwable {
			if (chooseable != null) {
				chooseable.choose();
				refresh();
			}
		}
		
		@Override
		public void updateState() throws Throwable {
			super.updateState();
		}
	}
	
	private class OpenAction extends BaseAction {

		public OpenAction(Component parent) {
			super(parent);
			setName("Open");
			setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_DOWN_MASK));
			setIcon("x16/row_edit.png");
		}

		@Override
		public void perform(ActionEvent e) throws Throwable {
			E selected = getSelectorTable().getSelectedElement();
			if (openable != null && selected != null) {
				E ret = openable.open(SelectorPanel.this, selected);
				if (ret != null) {
					emulateAction(refreshAction);
				}
			}
		}
		
		@Override
		public void updateState() throws Throwable {
			this.setEnabled(getSelectorTable().getSelectedElement() != null);
		}
	}
	
	public static<E> SelectorDialog<E> createDialog(Component parent, String title, Class<E> entityClass, 
			ILoadable<E> loadable, 
			IOpenable<E> openable, 
			INewable<E> newable, 
			IDeletable<E> deletable,
			final ISelectable<E> selectable, 
			IChooseable<E> choosable, IFilterable<E> filterable, Class<? extends EnumBeanTableModel<E, ?>> tableModelClass) {
		
		final SelectorDialog<E> dialog = new SelectorDialog<E>(DialogUtilities.getWindowForComponent(parent), ModalityType.DOCUMENT_MODAL, null);
		ISelectable<E> newSelectable = new ISelectable<E>() {
			public List<E> select(List<E> entity) {
				entity = selectable.select(entity);
				dialog.dispose();
				return entity;
			};
		};
		SelectorPanel<E> selectorPanel = new SelectorPanel<E>(entityClass, loadable, openable, newable, deletable, newSelectable, choosable, filterable, tableModelClass);
		dialog.setSelectorPanel(selectorPanel);
		dialog.getContentPane().add(selectorPanel, BorderLayout.CENTER);
		dialog.setTitle(title);
		dialog.pack();
		dialog.setLocationRelativeTo(DialogUtilities.getWindowForComponent(parent));
		return dialog;
	}
	
	public static class SelectorDialog<E> extends BaseDialog {

		private static final long serialVersionUID = 1L;
		private SelectorPanel<E> selectorPanel;
		
		public SelectorDialog(Window owner, ModalityType modalityType, SelectorPanel<E> panel) {
			super(owner, modalityType);
			this.selectorPanel = panel;
		}
		
		void setSelectorPanel(SelectorPanel<E> selectorPanel) {
			this.selectorPanel = selectorPanel;
		}
		
		public SelectorPanel<E> getSelectorPanel() {
			return selectorPanel;
		}
		
		public E getSelectedElement() {
			return selectorPanel.getSelectedElement();
		}
		
		public List<E> getSelectedElements() {
			return selectorPanel.getSelectedElements();
		}
	}
	
}
